﻿using System;
using System.Collections.Generic;
using System.Timers;
using System.Windows;
using System.Windows.Threading;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Command;
using Bymed.SpineScan.Service.Shared;
using Bymed.SpineScan.WPF.Model;
using Bymed.SpineScan.WPF.Shared;
using Bymed.SpineScan.Service;

namespace Bymed.SpineScan.WPF.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : MyViewModelBase
    {
        public const string ReturnHomeMessageToken = "MainViewModeReturnHomeMessage";
        public const string GoToCustomizedTestItemPageMessageToken = "MainViewModelGoToCustomizedTestItemPageMessage";
        public const string GoToAttendingDoctorSetupPageMessageToken = "MainViewModelGoToAttendingDoctorSetupPageMessage";
        public const string GoToDiagnosticMessageReferenceSetupPageMessageToken = "MainViewModelGoToDiagnosticMessageReferenceSetupPageMessage";
        public const string GoToDataManagementPageMessageToken = "MainViewModelGoToDataManagementPageMessage";
        public const string GoToBasicConfigurationPageMessageToken = "MainViewModelGoToBasicConfigurationPageMessage";
        public const string GoToDeviceConfigurationPageMessageToken = "MainViewModelGoToDeviceConfigurationPageMessage";
        public const string GoToHelpPageMessageToken = "MainViewModelGoToHelpPageMessage";
        public const string SendReceivedTestMessageMessageToken = "MainViewModelSendReceivedTestMessageMessageToken";
        private TestMessageReceiver _testMessageReceiver;
        private GenuineCheckService _genuineCheckService;
        private IList<Type> _includedPageList;

        private enum DayOfWeekChinese
        {
            星期日 = 0,
            星期一 = 1,
            星期二 = 2,
            星期三 = 3,
            星期四 = 4,
            星期五 = 5,
            星期六 = 6
        }

        private Timer _timer;

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel(IDataService dataService)
            : base(dataService)
        {
            _genuineCheckService = ServiceFactory.CreateGenuineCheckService();

            ResetProperties();
            StartTimerToDisplaySystemDateTime();
            StartTestMessageReceiver();
            InitializeIncludedPageList();
        }

        public override void Cleanup()
        {
            base.Cleanup();
            StopTestMessageReceiver();
        }

        /// <summary>
        /// Register all necessary messages. It is called by constructor in base class.
        /// </summary>
        protected override void RegisterMessages()
        {
            base.RegisterMessages();

            Messenger.Default.Register<string>(this, MyViewModelBase.RequestDisplayErrorMessageToken, (msg) =>
            {
                SetupErrorMessage(msg);
            });

            Messenger.Default.Register<string>(this, MyViewModelBase.RequestClearErrorMessageToken, (msg) =>
            {
                ClearErrorMessage();
            });

            Messenger.Default.Register<string>(this, MyViewModelBase.RequestDisplayMessageToken, (msg) =>
            {
                SetupSummaryrMessage(msg);
            });

            Messenger.Default.Register<string>(this, MyViewModelBase.RequestClearMessageToken, (msg) =>
            {
                ClearSummaryrMessage();
            });

            Messenger.Default.Register<int>(this, SelectTestItemViewModel.GenuineCheckFailedMessageToken, (msg) =>
            {
                GenuineCheck(_genuineCheckService.GetSerialNumber());
            });
        }

        public override void ResetProperties()
        {
            base.ResetProperties();

            GenuineCheckPassed = false;
            GenuineCheckResult = "等待验证硬件设备合法性 ...";
            GenuineCheckDetail = "请连接硬件之后再开启开关";
        }

        /// <summary>
        /// The <see cref="GenuineCheckPassed" /> property's name.
        /// </summary>
        public const string GenuineCheckPassedPropertyName = "GenuineCheckPassed";

        private bool _genuineCheckPassed = false;

        /// <summary>
        /// Sets and gets the GenuineCheckPassed property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool GenuineCheckPassed
        {
            get
            {
                return _genuineCheckPassed;
            }

            set
            {
                if (_genuineCheckPassed == value)
                {
                    return;
                }

                RaisePropertyChanging(GenuineCheckPassedPropertyName);
                _genuineCheckPassed = value;
                RaisePropertyChanged(GenuineCheckPassedPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="NeedToExcludeFromRestriction" /> property's name.
        /// </summary>
        public const string NeedToExcludeFromRestrictionPropertyName = "NeedToExcludeFromRestriction";

        private bool _needToExcludeFromRestriction = true;

        /// <summary>
        /// Sets and gets the NeedToExcludeFromRestriction property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool NeedToExcludeFromRestriction
        {
            get
            {
                return _needToExcludeFromRestriction;
            }

            set
            {
                if (_needToExcludeFromRestriction == value)
                {
                    return;
                }

                RaisePropertyChanging(NeedToExcludeFromRestrictionPropertyName);
                _needToExcludeFromRestriction = value;
                RaisePropertyChanged(NeedToExcludeFromRestrictionPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="GenuineCheckResult" /> property's name.
        /// </summary>
        public const string GenuineCheckResultPropertyName = "GenuineCheckResult";

        private string _genuineCheckResult = string.Empty;

        /// <summary>
        /// Sets and gets the GenuineCheckResult property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string GenuineCheckResult
        {
            get
            {
                return _genuineCheckResult;
            }

            set
            {
                if (_genuineCheckResult == value)
                {
                    return;
                }

                RaisePropertyChanging(GenuineCheckResultPropertyName);
                _genuineCheckResult = value;
                RaisePropertyChanged(GenuineCheckResultPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="GenuineCheckDetail" /> property's name.
        /// </summary>
        public const string GenuineCheckDetailPropertyName = "GenuineCheckDetail";

        private string _genuineCheckDetail = string.Empty;

        /// <summary>
        /// Sets and gets the GenuineCheckDetail property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string GenuineCheckDetail
        {
            get
            {
                return _genuineCheckDetail;
            }

            set
            {
                if (_genuineCheckDetail == value)
                {
                    return;
                }

                RaisePropertyChanging(GenuineCheckDetailPropertyName);
                _genuineCheckDetail = value;
                RaisePropertyChanged(GenuineCheckDetailPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="CurrentMainFrameContent" /> property's name.
        /// </summary>
        public const string CurrentMainFrameContentPropertyName = "CurrentMainFrameContent";

        private object _currentMainFrameContent = null;

        /// <summary>
        /// Sets and gets the CurrentMainFrameContent property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public object CurrentMainFrameContent
        {
            get
            {
                return _currentMainFrameContent;
            }

            set
            {
                if (_currentMainFrameContent == value)
                {
                    return;
                }

                RaisePropertyChanging(CurrentMainFrameContentPropertyName);
                _currentMainFrameContent = value;
                RaisePropertyChanged(CurrentMainFrameContentPropertyName);

                // Check if current Page needs to be excluded from restriction list.
                NeedToExcludeFromRestriction = !_includedPageList.Contains(CurrentMainFrameContent.GetType()) ? true : false;
            }
        }

        /// <summary>
        /// The <see cref="CurrentSystemDateTime" /> property's name.
        /// </summary>
        public const string CurrentSystemDateTimePropertyName = "CurrentSystemDateTime";

        private string _currentSystemDateTime;

        /// <summary>
        /// Sets and gets the CurrentSystemDateTime property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string CurrentSystemDateTime
        {
            get
            {
                return _currentSystemDateTime;
            }

            set
            {
                if (_currentSystemDateTime == value)
                {
                    return;
                }

                RaisePropertyChanging(CurrentSystemDateTimePropertyName);
                _currentSystemDateTime = value;
                RaisePropertyChanged(CurrentSystemDateTimePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="ErrorMessage" /> property's name.
        /// </summary>
        public const string ErrorMessagePropertyName = "ErrorMessage";

        private string _errorMessage = string.Empty;

        /// <summary>
        /// Sets and gets the ErrorMessage property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string ErrorMessage
        {
            get
            {
                return _errorMessage;
            }

            set
            {
                if (_errorMessage == value)
                {
                    return;
                }

                RaisePropertyChanging(ErrorMessagePropertyName);
                _errorMessage = value;
                RaisePropertyChanged(ErrorMessagePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="ErrorMessageVisibility" /> property's name.
        /// </summary>
        public const string ErrorMessageVisibilityPropertyName = "ErrorMessageVisibility";

        private Visibility _errorMessageVisibility = Visibility.Collapsed;

        /// <summary>
        /// Sets and gets the ErrorMessageVisibility property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public Visibility ErrorMessageVisibility
        {
            get
            {
                return _errorMessageVisibility;
            }

            set
            {
                if (_errorMessageVisibility == value)
                {
                    return;
                }

                RaisePropertyChanging(ErrorMessageVisibilityPropertyName);
                _errorMessageVisibility = value;
                RaisePropertyChanged(ErrorMessageVisibilityPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="SummaryMessage" /> property's name.
        /// </summary>
        public const string SummaryMessagePropertyName = "SummaryMessage";

        private string _summaryMessage = string.Empty;

        /// <summary>
        /// Sets and gets the SummaryMessage property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string SummaryMessage
        {
            get
            {
                return _summaryMessage;
            }

            set
            {
                if (_summaryMessage == value)
                {
                    return;
                }

                RaisePropertyChanging(SummaryMessagePropertyName);
                _summaryMessage = value;
                RaisePropertyChanged(SummaryMessagePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="SummaryMessageVisibility" /> property's name.
        /// </summary>
        public const string SummaryMessageVisibilityPropertyName = "SummaryMessageVisibility";

        private Visibility _summaryMessageVisibility = Visibility.Collapsed;

        /// <summary>
        /// Sets and gets the SummaryMessageVisibility property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public Visibility SummaryMessageVisibility
        {
            get
            {
                return _summaryMessageVisibility;
            }

            set
            {
                if (_summaryMessageVisibility == value)
                {
                    return;
                }

                RaisePropertyChanging(SummaryMessageVisibilityPropertyName);
                _summaryMessageVisibility = value;
                RaisePropertyChanged(SummaryMessageVisibilityPropertyName);
            }
        }

        private RelayCommand _returnHomeCommand;

        /// <summary>
        /// Gets the ReturnHomeCommand.
        /// </summary>
        public RelayCommand ReturnHomeCommand
        {
            get
            {
                return _returnHomeCommand
                    ?? (_returnHomeCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send ReturnHomeMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, ReturnHomeMessageToken);
                                          }));
            }
        }

        private RelayCommand _customizedTestItemCommand;

        /// <summary>
        /// Gets the CustomizedTestItemCommand.
        /// </summary>
        public RelayCommand CustomizedTestItemCommand
        {
            get
            {
                return _customizedTestItemCommand
                    ?? (_customizedTestItemCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToCustomizedTestItemPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToCustomizedTestItemPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _attendingDoctorSetupCommand;

        /// <summary>
        /// Gets the AttendingDoctorSetupCommand.
        /// </summary>
        public RelayCommand AttendingDoctorSetupCommand
        {
            get
            {
                return _attendingDoctorSetupCommand
                    ?? (_attendingDoctorSetupCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToAttendingDoctorSetupPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToAttendingDoctorSetupPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _diagnosticMessageReferenceSetupCommand;

        /// <summary>
        /// Gets the DiagnosticMessageReferenceSetupCommand.
        /// </summary>
        public RelayCommand DiagnosticMessageReferenceSetupCommand
        {
            get
            {
                return _diagnosticMessageReferenceSetupCommand
                    ?? (_diagnosticMessageReferenceSetupCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToDiagnosticMessageSetupPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToDiagnosticMessageReferenceSetupPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _dataManagementCommand;

        /// <summary>
        /// Gets the DataManagementCommand.
        /// </summary>
        public RelayCommand DataManagementCommand
        {
            get
            {
                return _dataManagementCommand
                    ?? (_dataManagementCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToDataManagementPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToDataManagementPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _basicConfigurationCommand;

        /// <summary>
        /// Gets the BasicConfigurationCommand.
        /// </summary>
        public RelayCommand BasicConfigurationCommand
        {
            get
            {
                return _basicConfigurationCommand
                    ?? (_basicConfigurationCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToBasicConfigurationPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToBasicConfigurationPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _deviceConfigurationCommand;

        /// <summary>
        /// Gets the DeviceConfigurationCommand.
        /// </summary>
        public RelayCommand DeviceConfigurationCommand
        {
            get
            {
                return _deviceConfigurationCommand
                    ?? (_deviceConfigurationCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToDeviceConfigurationPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToDeviceConfigurationPageMessageToken);
                                          }));
            }
        }

        private RelayCommand _helpCommand;

        /// <summary>
        /// Gets the HelpCommand.
        /// </summary>
        public RelayCommand HelpCommand
        {
            get
            {
                return _helpCommand
                    ?? (_helpCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send GoToHelpPageMessage to PageBase.
                                              // Use Command stead of Click event because of the Storyboard trigger.
                                              Messenger.Default.Send<int>(0, GoToHelpPageMessageToken);
                                          }));
            }
        }

        /// <summary>
        /// Setup the ErrorMessage.
        /// </summary>
        /// <param name="msg">The string of error message.</param>
        private void SetupErrorMessage(string msg)
        {
            if (!ErrorMessage.Contains(msg))
            {
                ErrorMessage += msg + ";";
            }

            ErrorMessageVisibility = string.IsNullOrEmpty(msg) ? Visibility.Collapsed : Visibility.Visible;
        }

        /// <summary>
        /// Setup the ErrorMessage.
        /// </summary>
        private void ClearErrorMessage()
        {
            ErrorMessage = string.Empty;
            ErrorMessageVisibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Setup the SummaryrMessage.
        /// </summary>
        /// <param name="msg">The string of summary message.</param>
        private void SetupSummaryrMessage(string msg)
        {
            if (!SummaryMessage.Contains(msg))
            {
                SummaryMessage += msg + ";";
            }

            SummaryMessageVisibility = string.IsNullOrEmpty(msg) ? Visibility.Collapsed : Visibility.Visible;
        }

        /// <summary>
        /// Clear the SummaryrMessage.
        /// </summary>
        private void ClearSummaryrMessage()
        {
            SummaryMessage = string.Empty;
            SummaryMessageVisibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Start the timer to display current system date time.
        /// </summary>
        private void StartTimerToDisplaySystemDateTime()
        {
            CurrentSystemDateTime = "获取中 ...";
            _timer = new Timer(1000);
            _timer.Elapsed += Timer_Elapsed;
            _timer.Start();
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            var currentDateTime = DateTime.Now;

            CurrentSystemDateTime = string.Format("{0} {1}",
                currentDateTime.ToString(GeneralUtility.LongDateTimeFormat),
                (DayOfWeekChinese)currentDateTime.DayOfWeek);
        }

        /// <summary>
        /// Start the TestMessageReceiver to open the serial port and ready to receive the message.
        /// </summary>
        private void StartTestMessageReceiver()
        {
            _testMessageReceiver = new TestMessageReceiver();
            _testMessageReceiver.MessageReceived += TestMessageReceiver_MessageReceived;
            _testMessageReceiver.DeviceConnectivityChanged += TestMessageReceiver_DeviceConnectivityChanged;
            _testMessageReceiver.Start();
        }

        private void TestMessageReceiver_MessageReceived(object sender, EventArgs e)
        {
            var message = ((TestMessageReceiver)sender).Message;

            if (_genuineCheckService.CurrentCheckStatus != GenuineCheckStatus.Passed)
            {
                GenuineCheck(message);

                return;
            }

            // Send SendReceivedTestMessageMessage to StartTestViewModel.
            // MUST use RunAction, otherwise the below exception will occur.
            // The calling thread cannot access this object because a different thread owns it.
            ViewHelper.RunAction(new Action(() =>
            {
                Messenger.Default.Send<string>(message, SendReceivedTestMessageMessageToken);
            }));
        }

        private void TestMessageReceiver_DeviceConnectivityChanged(object sender, EventArgs e)
        {
            CheckDeviceConnectivityStatus(((TestMessageReceiver)sender).IsDeviceConnected);
        }

        /// <summary>
        /// Stop test message receiver.
        /// </summary>
        private void StopTestMessageReceiver()
        {
            // Stop the test message receiver
            _testMessageReceiver.MessageReceived -= TestMessageReceiver_MessageReceived;
            _testMessageReceiver.DeviceConnectivityChanged -= TestMessageReceiver_DeviceConnectivityChanged;

            _testMessageReceiver.Stop();
            _testMessageReceiver.Dispose();
        }

        /// <summary>
        /// Initialize the included page list for restrction check.
        /// If current device Serial Number does not matched, then these pages will be blocked.
        /// </summary>
        private void InitializeIncludedPageList()
        {
            _includedPageList = new List<Type>();
            _includedPageList.Add(typeof(SelectTestItemPage));
            _includedPageList.Add(typeof(StartTestPage));
        }

        /// <summary>
        /// Genuine check. If current device Serial Number does not matched and current
        /// page belongs to included restriction list, then user cannot proceed anymore.
        /// </summary>
        /// <param name="message">The message from device that contains the Serial Number.</param>
        private void GenuineCheck(string message)
        {
            var serialNumber = _genuineCheckService.GetSerialNumber(message);
            var checkStatus = _genuineCheckService.Check(serialNumber);
            _genuineCheckService.SaveSerialNumber(serialNumber);

            GenuineCheckPassed = _genuineCheckService.CurrentCheckStatus == GenuineCheckStatus.Passed ? true : false;
            NeedToExcludeFromRestriction = !_includedPageList.Contains(CurrentMainFrameContent.GetType()) ? true : false;

            // If genuine check failed.
            if (!GenuineCheckPassed)
            {
                GenuineCheckResult = "验证未通过";
                GenuineCheckDetail = "系统注册码不合法，请与系统提供商联系以获取技术支持。";

                // If current page is NOT restricted then display the message as device is NOT authorised.
                if (NeedToExcludeFromRestriction)
                {
                    RequestDisplayMessage("设备未授权");
                }
            }
        }

        /// <summary>
        /// Check the device connectivity status display the message.
        /// </summary>
        /// <param name="isDeviceConnected">If device is connected now.</param>
        private void CheckDeviceConnectivityStatus(bool isDeviceConnected)
        {
            // If current device is NOT connected
            if (!isDeviceConnected)
            {
                // Reset the genuine check status to Unchecked.
                if (_genuineCheckService.CurrentCheckStatus != GenuineCheckStatus.Unchecked)
                {
                    GenuineCheckPassed = false;
                    _genuineCheckService.CurrentCheckStatus = GenuineCheckStatus.Unchecked;

                    ResetProperties();
                }

                // If current page is NOT restricted then display the message as device is NOT connected.
                if (NeedToExcludeFromRestriction)
                {
                    RequestDisplayMessage("设备未连接");
                }
            }
            else
            {
                // If current device is connected but failed on genuine check.
                if (_genuineCheckService.CurrentCheckStatus == GenuineCheckStatus.Failed)
                {
                    RequestDisplayMessage("设备未授权");
                }
                else
                {
                    RequestClearMessage();
                }
            }
        }
    }
}